package org.act.xservices.rt.metaset;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;


public class ClassFinder {
	
    private static boolean debug = false;
	
    // Define system property "ClassFinder.debug" to get output
    static {
        // Use try/catch block to support applets, which throws
        // SecurityException out of this code.
        try {
            String val = System.getProperty("ClassFinder.debug");
            // Allow simply setting the prop to turn on debug
            debug = val != null && (! "false".equals(val));
        } catch (SecurityException se) {
            debug = false;
        }
    }
    
    private static void dPrint(String msg) {
        if (debug) {
            System.err.println("JAXP: " + msg);
        }
    }
    
    
    /**
     * Finds the implementation Class object in the specified order.  Main
     * entry point.
     * @return Class object of factory, never null
     *
     * @param systemPropertyId      Name of the systemPropertyId of the class to find, 
     * 								same as a property name
     * @param fallbackClassName     Implementation class name, if nothing else
     *                              is found.  Use null to mean no fallback.
     *
     * Package private so this code can be shared.
     */
    public static Object findBySystemPropertyId(String systemPropertyId, 
    		String fallbackClassName) throws ConfigurationError {        
        
        ClassLoader classLoader = ClassFinder.class.getClassLoader();;
        
        dPrint("find systemPropertyId =" + systemPropertyId);
        
        // Use the system property first
        try {
            String systemProp = System.getProperty(systemPropertyId);
            if( systemProp!=null) {                
                dPrint("found system property, value=" + systemProp);
                return newInstance(systemProp, classLoader, true );
            }
        } catch (SecurityException se) {
            //if first option fails due to any reason we should try next option in the
            //look up algorithm.
        }

        if (fallbackClassName == null) {
            throw new ConfigurationError(
                "Provider for " + systemPropertyId + " cannot be found", null);
        }

        dPrint("loaded from fallback value: " + fallbackClassName);
        return newInstance(fallbackClassName, classLoader, true);
    }
    
	
	
    /**
     * Finds the implementation Class object in the specified order.  Main
     * entry point.
     * @return Class object of factory, never null
     *
     * @param className      Name of the systemPropertyId of the class to find, 
     * 								same as a property name
     * @param fallbackClassName     Implementation class name, if nothing else
     *                              is found.  Use null to mean no fallback.
     *
     * Package private so this code can be shared.
     */
    public static Object findByClassName(String className, 
    		String fallbackClassName) throws ConfigurationError {        
        
        ClassLoader classLoader = ClassFinder.class.getClassLoader();
        
        dPrint("find className =" + className);
        
        // Use the className first
        try {
           if( className!=null) {                
              return newInstance(className, classLoader, true );
           }
        } catch (SecurityException se) {
            //if first option fails due to any reason we should try next option in the
            //look up algorithm.
        }

        if (fallbackClassName == null) {
            throw new ConfigurationError(
                "Provider for " + className + " cannot be found", null);
        }
        
        dPrint("loaded from fallback value: " + fallbackClassName);
        return newInstance(fallbackClassName, classLoader, true);
    }
    
    
    public static Object find(String systemPropertyId, 
    		String fallbackClassName) throws ConfigurationError {
    	ClassLoader classLoader = ClassFinder.class.getClassLoader();
    	return find(systemPropertyId, fallbackClassName, classLoader);
    }
    
    /**
     * Finds the implementation Class object in the specified order.  Main
     * entry point.
     * @return Class object of factory, never null
     *
     * @param systemPropertyId      Name of the systemPropertyId of the class to find, 
     * 								same as a property name
     * @param fallbackClassName     Implementation class name, if nothing else
     *                              is found.  Use null to mean no fallback.
     *
     * Package private so this code can be shared.
     */
    public static Object find(String systemPropertyId, String fallbackClassName,
                       ClassLoader classLoader) throws ConfigurationError {
   

        // Use the system property first
        try {
            String systemProp = System.getProperty( systemPropertyId );
            if( systemProp!=null) {
            	dPrint("found system property" + systemProp);
                return newInstance(systemProp, classLoader, true);
            }
        } catch (SecurityException se) {
        }

        // try to read from $java.home/lib/xml.properties
        // ignore by lizq 20090107
//        try {
//            String javah=System.getProperty( "java.home" );
//            String configFile = javah + File.separator +
//                "lib" + File.separator + "jaxp.properties";
//            File f=new File( configFile );
//            if( f.exists()) {
//                Properties props=new Properties();
//                props.load( new FileInputStream(f));
//                String factoryClassName = props.getProperty(systemPropertyId);
//                dPrint("found java.home property " + factoryClassName);
//                return newInstance(factoryClassName, classLoader, true);
//            }
//        } catch(Exception ex ) {
//            if( debug ) ex.printStackTrace();
//        }

        String serviceId = "META-INF/services/" + systemPropertyId;
        // try to find services in CLASSPATH
        try {
            InputStream is=null;
            if (classLoader == null) {
                is=ClassLoader.getSystemResourceAsStream( serviceId );
            } else {
                is=classLoader.getResourceAsStream( serviceId );
            }
        
            if( is!=null ) {
            	dPrint("found " + serviceId);
                BufferedReader rd =
                    new BufferedReader(new InputStreamReader(is, "UTF-8"));
        
                String factoryClassName = rd.readLine();
                rd.close();

                if (factoryClassName != null &&
                    ! "".equals(factoryClassName)) {
                	dPrint("loaded from services: " + factoryClassName);
                    return newInstance(factoryClassName, classLoader, true);
                }
            }
        } catch( Exception ex ) {
            if( debug ) ex.printStackTrace();
        }

        if (fallbackClassName == null) {
            throw new ConfigurationError(
                "Provider for " + systemPropertyId + " cannot be found", null);
        }

        dPrint("loaded from fallback value: " + fallbackClassName);
        return newInstance(fallbackClassName, classLoader, true);
    }
	

    /**
     * Create an instance of a class using the specified ClassLoader and
     * optionally fall back to the current ClassLoader if not found.
     *
     * @param className Name of the concrete class corresponding to the
     * service provider
     *
     * @param cl ClassLoader to use to load the class, null means to use
     * the bootstrap ClassLoader
     *
     * @param doFallback true if the current ClassLoader should be tried as
     * a fallback if the class is not found using cl
     */
    private static Object newInstance(String className, ClassLoader cl, 
    		boolean doFallback) throws ConfigurationError {
    	
        // assert(className != null);
        try {
            Class providerClass;
            if (cl == null) {
                // If classloader is null Use the bootstrap ClassLoader.  
                // Thus Class.forName(String) will use the current
                // ClassLoader which will be the bootstrap ClassLoader.
                providerClass = Class.forName(className);
            } else {
                try {
                    providerClass = cl.loadClass(className);
                } catch (ClassNotFoundException x) {
                    if (doFallback) {
                        // Fall back to current classloader
                        cl = ClassFinder.class.getClassLoader();
                        providerClass = cl.loadClass(className);
                    } else {
                        throw x;
                    }
                }
            }
                        
            Object instance = providerClass.newInstance();
            dPrint("created new instance of " + providerClass + " using ClassLoader: " + cl);
            return instance;
        } catch (ClassNotFoundException x) {
            throw new ConfigurationError(
                "Provider " + className + " not found", x);
        } catch (Exception x) {
            throw new ConfigurationError(
                "Provider " + className + " could not be instantiated: " + x, x);
        }
    }
  
}
